xtask\tasks\fmt/
house_rules.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use crate::Xtask;
5use crate::fs_helpers::git_diffed;
6use crate::fs_helpers::git_ls_files;
7use clap::Parser;
8use std::path::PathBuf;
9
10const PATH_TO_HOUSE_RULES_RS: &str = file!(); // used by `cfg_target_arch`
11
12mod autogen_comment;
13mod cfg_target_arch;
14mod copyright;
15mod crate_name_nodash;
16mod package_info;
17mod repr_packed;
18mod trailing_newline;
19mod unsafe_code_comment;
20
21#[derive(Parser)]
22#[clap(about = r#"Collection of misc formatting "house rules"
23
24RULES:
25
26    - enforce the presence of the standard Microsoft copyright header
27    - enforce in-repo crate names don't use '-' in their name (use '_' instead)
28    - enforce Cargo.toml files don't include autogenerated "see more keys" comments
29    - enforce Cargo.toml files don't contain author or version fields
30    - enforce files end with a single trailing newline
31    - deny usage of `#[repr(packed)]` (you want `#[repr(C, packed)]`)
32    - justify usage of `cfg(target_arch = ...)` (use `guest_arch` instead!)
33    - justify usage of `expect(unsafe_code)` with an UNSAFETY comment
34    "#)]
35pub struct HouseRules {
36    /// Attempt to fix formatting issues
37    #[clap(long)]
38    pub fix: bool,
39
40    /// Only run checks on files that are currently diffed
41    #[clap(long, conflicts_with = "files")]
42    pub only_diffed: bool,
43
44    /// A list of files to check
45    ///
46    /// If no files were provided, all files in-tree will be checked
47    pub files: Vec<PathBuf>,
48
49    /// Don't run the copyright header check
50    #[clap(long)]
51    pub skip_copyright: bool,
52
53    /// Don't run the autogenerated Cargo.toml "see more keys" comment check
54    #[clap(long)]
55    pub skip_autogen_comment: bool,
56
57    /// Don't run the Cargo.toml author and version field checks
58    #[clap(long)]
59    pub skip_package_info: bool,
60
61    /// Don't run the trailing newline check
62    #[clap(long)]
63    pub skip_trailing_newline: bool,
64
65    /// Don't run the crate name check
66    #[clap(long)]
67    pub skip_crate_name: bool,
68
69    /// Don't run the `#[repr(packed)]` check
70    #[clap(long)]
71    pub skip_repr_packed: bool,
72
73    /// Don't run the `#[cfg(target_arch)]` check
74    #[clap(long)]
75    pub skip_cfg_target_arch: bool,
76
77    /// Don't run the `#[expect(unsafe_code)]` comment check
78    #[clap(long)]
79    pub skip_unsafe_code_comment: bool,
80}
81
82impl HouseRules {
83    /// Initialize `HouseRules` with all passes enabled
84    pub fn all_passes(fix: bool, only_diffed: bool) -> HouseRules {
85        HouseRules {
86            fix,
87            only_diffed,
88            files: Vec::new(),
89            skip_copyright: false,
90            skip_autogen_comment: false,
91            skip_package_info: false,
92            skip_trailing_newline: false,
93            skip_crate_name: false,
94            skip_repr_packed: false,
95            skip_cfg_target_arch: false,
96            skip_unsafe_code_comment: false,
97        }
98    }
99}
100
101#[derive(Debug)]
102enum Files {
103    All,
104    OnlyDiffed,
105    Specific(Vec<PathBuf>),
106}
107
108impl Xtask for HouseRules {
109    fn run(self, ctx: crate::XtaskCtx) -> anyhow::Result<()> {
110        let files = if self.only_diffed {
111            Files::OnlyDiffed
112        } else if self.files.is_empty() {
113            Files::All
114        } else {
115            Files::Specific(self.files)
116        };
117
118        log::trace!("running house-rules on {:?}", files);
119
120        let files = match files {
121            Files::All => git_ls_files()?,
122            Files::OnlyDiffed => git_diffed(ctx.in_git_hook)?,
123            Files::Specific(files) => files,
124        };
125
126        let mut errors = Vec::new();
127        for path in files {
128            if !self.skip_copyright {
129                if let Err(e) = copyright::check_copyright(&path, self.fix) {
130                    errors.push(e)
131                }
132            }
133
134            if !self.skip_autogen_comment {
135                if let Err(e) = autogen_comment::check_autogen_comment(&path, self.fix) {
136                    errors.push(e)
137                }
138            }
139
140            if !self.skip_package_info {
141                if let Err(e) = package_info::check_package_info(&path, self.fix) {
142                    errors.push(e)
143                }
144            }
145
146            if !self.skip_trailing_newline {
147                if let Err(e) = trailing_newline::check_trailing_newline(&path, self.fix) {
148                    errors.push(e)
149                }
150            }
151
152            if !self.skip_crate_name {
153                if let Err(e) = crate_name_nodash::check_crate_name_nodash(&path) {
154                    errors.push(e)
155                }
156            }
157
158            if !self.skip_repr_packed {
159                if let Err(e) = repr_packed::check_repr_packed(&path, self.fix) {
160                    errors.push(e)
161                }
162            }
163
164            if !self.skip_cfg_target_arch {
165                if let Err(e) = cfg_target_arch::check_cfg_target_arch(&path, self.fix) {
166                    errors.push(e)
167                }
168            }
169
170            if !self.skip_unsafe_code_comment {
171                if let Err(e) = unsafe_code_comment::check_unsafe_code_comment(&path, self.fix) {
172                    errors.push(e)
173                }
174            }
175        }
176
177        for e in &errors {
178            log::error!("{:#}", e);
179        }
180
181        if !errors.is_empty() && !self.fix {
182            Err(anyhow::anyhow!("`house-rules` found formatting errors"))
183        } else {
184            Ok(())
185        }
186    }
187}